feat(fumadb): add aggregate and keyset query support - #1119
Conversation
Greptile SummaryThis PR adds SQL-pushed JSON-document aggregate primitives and keyset pagination to FumaDB, exposing them through the plugin storage SDK facade. All previous review concerns (empty
Confidence Score: 5/5Safe to merge — all five new query operations apply table read policies before reaching the adapter, and the empty-or / explicit-operator issues from the previous review are resolved. The implementation is correct and well-tested across memory, SQLite, and policy harnesses. The null-aware keyset cursor logic is exercised end-to-end (including the nullable-sort-column truncation regression). JSON path quoting, LIKE escaping, and empty composite filter semantics all have explicit parity tests between the memory and Drizzle adapters. No files require special attention. The most complex file (drizzle/query.ts) is fully covered by the aggregate.test.ts harness for SQLite, and the Postgres-specific paths (percentile_cont, nulls last/first) are intentionally deferred to a future Postgres test harness as noted in the PR description. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Plugin as Plugin Code
participant Facade as PluginStorageFacade
participant CoreDb as CoreDb
participant ORM as toORM (orm/index.ts)
participant Adapter as Drizzle/Memory Adapter
Plugin->>Facade: "storage.runs.aggregate.groupCount({ field, where })"
Facade->>Facade: validate indexed fields
Facade->>Facade: pluginStorageWhereToJsonFilter(where)
Facade->>CoreDb: "jsonGroupCount(plugin_storage, { column, where, filter })"
CoreDb->>ORM: jsonGroupCount(name, options)
ORM->>ORM: requireJsonOp + compileScopedWhere
ORM->>ORM: applyReadPolicies
ORM->>Adapter: "jsonGroupCount(table, { column, where, filter, path })"
Adapter-->>ORM: JsonGroupCountRow[]
ORM-->>Facade: JsonGroupCountRow[]
Facade-->>Plugin: PluginStorageGroupCount[]
Plugin->>Facade: "storage.runs.queryKeyset({ orderBy, cursor, limit })"
Facade->>Facade: validate indexed fields + limit
Facade->>CoreDb: "jsonPage(plugin_storage, { orderBy, keyColumn, cursor, limit })"
CoreDb->>ORM: jsonPage(name, options)
ORM->>ORM: requireJsonOp + compileScopedWhere
ORM->>ORM: applyReadPolicies
ORM->>Adapter: "jsonPage(table, { orderBy, keyColumn, keyDirection, cursor, limit })"
Note over Adapter: null-aware cursor predicate OR-terms
Adapter-->>ORM: Row[]
ORM-->>Facade: Row[]
Facade->>Facade: build nextCursor from last entry
Facade-->>Plugin: "{ entries, nextCursor }"
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Plugin as Plugin Code
participant Facade as PluginStorageFacade
participant CoreDb as CoreDb
participant ORM as toORM (orm/index.ts)
participant Adapter as Drizzle/Memory Adapter
Plugin->>Facade: "storage.runs.aggregate.groupCount({ field, where })"
Facade->>Facade: validate indexed fields
Facade->>Facade: pluginStorageWhereToJsonFilter(where)
Facade->>CoreDb: "jsonGroupCount(plugin_storage, { column, where, filter })"
CoreDb->>ORM: jsonGroupCount(name, options)
ORM->>ORM: requireJsonOp + compileScopedWhere
ORM->>ORM: applyReadPolicies
ORM->>Adapter: "jsonGroupCount(table, { column, where, filter, path })"
Adapter-->>ORM: JsonGroupCountRow[]
ORM-->>Facade: JsonGroupCountRow[]
Facade-->>Plugin: PluginStorageGroupCount[]
Plugin->>Facade: "storage.runs.queryKeyset({ orderBy, cursor, limit })"
Facade->>Facade: validate indexed fields + limit
Facade->>CoreDb: "jsonPage(plugin_storage, { orderBy, keyColumn, cursor, limit })"
CoreDb->>ORM: jsonPage(name, options)
ORM->>ORM: requireJsonOp + compileScopedWhere
ORM->>ORM: applyReadPolicies
ORM->>Adapter: "jsonPage(table, { orderBy, keyColumn, keyDirection, cursor, limit })"
Note over Adapter: null-aware cursor predicate OR-terms
Adapter-->>ORM: Row[]
ORM-->>Facade: Row[]
Facade->>Facade: build nextCursor from last entry
Facade-->>Plugin: "{ entries, nextCursor }"
Reviews (3): Last reviewed commit: "fix(fumadb): preserve empty or filter se..." | Re-trigger Greptile |
## Summary Mirrors the review-hardening deltas from upstream [UsefulSoftwareCo#1119](UsefulSoftwareCo#1119) onto `dev`. `dev` already contains the broader FumaDB aggregate and keyset query feature, so this PR only carries the remaining drift from the upstream review loop. ## Changes - Preserve memory and Drizzle parity for empty composite filters by compiling empty JSON `or` filters to a constant false SQL predicate. - Add regression coverage for empty `or` and empty `and` filters across the aggregate harness. - Replace nested plugin-storage operator selection with an explicit typed JSON compare-operator map. - Document SQLite percentile behavior on the public FumaDB and plugin-storage stats inputs. ## Intentional Differences From Upstream UsefulSoftwareCo#1119 - This PR does not re-add the full aggregate and keyset implementation because `dev` already has that feature surface. - This PR does not touch the OpenAPI storage facade mock because `dev` already has the mock shape needed by the expanded collection facade. - This PR has no changeset because it only mirrors fixes to an existing `dev` feature surface. ## Tests - `bun run bootstrap` - `bun run --cwd packages/core/fumadb test -- src/query/aggregate.test.ts src/query/table-policy.test.ts` - `bun run --cwd packages/core/sdk test -- src/plugin-storage-aggregate.test.ts src/plugin-storage.test.ts` - `bun run --cwd packages/core/fumadb typecheck` - `bun run --cwd packages/core/sdk typecheck` - `bun run typecheck` - `./node_modules/.bin/oxfmt --check packages/core/fumadb/src/adapters/drizzle/query.ts packages/core/fumadb/src/query/aggregate.test.ts packages/core/fumadb/src/query/aggregate.ts packages/core/sdk/src/executor.ts packages/core/sdk/src/plugin-storage.ts`
## Summary Mirrors the review-hardening deltas from upstream [UsefulSoftwareCo#1119](UsefulSoftwareCo#1119) onto `dev`. `dev` already contains the broader FumaDB aggregate and keyset query feature, so this PR only carries the remaining drift from the upstream review loop. ## Changes - Preserve memory and Drizzle parity for empty composite filters by compiling empty JSON `or` filters to a constant false SQL predicate. - Add regression coverage for empty `or` and empty `and` filters across the aggregate harness. - Replace nested plugin-storage operator selection with an explicit typed JSON compare-operator map. - Document SQLite percentile behavior on the public FumaDB and plugin-storage stats inputs. ## Intentional Differences From Upstream UsefulSoftwareCo#1119 - This PR does not re-add the full aggregate and keyset implementation because `dev` already has that feature surface. - This PR does not touch the OpenAPI storage facade mock because `dev` already has the mock shape needed by the expanded collection facade. - This PR has no changeset because it only mirrors fixes to an existing `dev` feature surface. ## Tests - `bun run bootstrap` - `bun run --cwd packages/core/fumadb test -- src/query/aggregate.test.ts src/query/table-policy.test.ts` - `bun run --cwd packages/core/sdk test -- src/plugin-storage-aggregate.test.ts src/plugin-storage.test.ts` - `bun run --cwd packages/core/fumadb typecheck` - `bun run --cwd packages/core/sdk typecheck` - `bun run typecheck` - `./node_modules/.bin/oxfmt --check packages/core/fumadb/src/adapters/drizzle/query.ts packages/core/fumadb/src/query/aggregate.test.ts packages/core/fumadb/src/query/aggregate.ts packages/core/sdk/src/executor.ts packages/core/sdk/src/plugin-storage.ts`
3852cc4 to
c9e1fb3
Compare
|
Rebased onto current main and refreshed at c9e1fb3. Branch-focused FumaDB, SDK, and OpenAPI tests; full package suites; format; lint; typecheck; unit tests; and 35 of 36 current GitHub checks pass. The sole red check is unrelated selfhost E2E infrastructure: admin-users-pager.test.ts uses a global getByRole button query for Next, which matched both the pager button and a generated user-row button named Next, causing a Playwright strict-mode failure. The affected storage tests and the previously failing OAuth scenario pass locally. |
da98e37 to
6d210fe
Compare
Add reusable JSON-document aggregate and keyset pagination primitives across FumaDB memory and Drizzle adapters. Expose the pushdown through plugin storage aggregate and queryKeyset facades with focused coverage for SQLite parity, null handling, path escaping, policy scoping, and unsupported adapters.
6d210fe to
58c8a98
Compare
Summary
Add policy-scoped JSON-document aggregation and keyset pagination to FumaDB, with memory and Drizzle implementations, and expose both through indexed plugin-storage collections.
API
LIKE, SQL nulls, percentiles, and nullable cursors.Validation
Execution history delivery map
Prerequisites:
Follow-up PR-sized diffs:
Execution history is the proven consumer for this query surface, not part of this PR. View the complete fork comparison.